package com.fuyun.rpc.client.proxy;

import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import com.fuyun.rpc.client.common.RpcTcpService;
import com.fuyun.rpc.client.config.RpcApplication;
import com.fuyun.rpc.client.fault.tolerant.TolerantStrategy;
import com.fuyun.rpc.client.fault.tolerant.TolerantStrategyFactory;
import com.fuyun.rpc.client.loadbalancing.LoadBalancer;
import com.fuyun.rpc.client.loadbalancing.LoadBalancerFactory;
import com.fuyun.rpc.client.monitor.MetricsCollector;
import com.fuyun.rpc.client.proxy.retrystrategy.RetryStrategy;
import com.fuyun.rpc.client.proxy.retrystrategy.RetryStrategyFactory;
import com.fuyun.rpc.client.registry.Registry;
import com.fuyun.rpc.client.registry.RegistryFactory;
import com.fuyun.rpc.common.domain.RpcRequest;
import com.fuyun.rpc.common.domain.RpcResponse;
import com.fuyun.rpc.common.domain.ServiceMetaInfo;
import com.fuyun.rpc.common.exception.ExceptionCode;
import com.fuyun.rpc.common.exception.RpcException;
import com.fuyun.rpc.common.serializer.Serializer;
import com.fuyun.rpc.common.serializer.SerializerFactory;
import com.fuyun.rpc.common.trace.TraceContext;
import com.fuyun.rpc.common.trace.TraceContextManager;
import com.fuyun.rpc.common.utils.MockDataUtil;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 客户端
 *
 * @author: zrq1351
 * @date: 2025/2/14
 */
@Slf4j
@AllArgsConstructor
public class ServiceProxy implements InvocationHandler {

	private RpcApplication rpcApplication;

	@Override
	public Object invoke(Object proxy, Method method, Object[] args) {

		// 客户端mock
		if (rpcApplication.getRpcConfig().getMock()) {
			return MockDataUtil.generate(method.getReturnType());
		}

		// 创建链路追踪上下文
		TraceContext traceContext = TraceContextManager.createNewContext(method.getDeclaringClass()
			                                                                 .getName(), method.getName());

		RpcRequest rpcRequest =
			RpcRequest.builder().serviceName(method.getDeclaringClass().getName()).methodName(method.getName())
				.parameterTypes(method.getParameterTypes())
				.args(args)
				.traceContext(traceContext)
				.build();

		// 添加认证信息
		if (rpcApplication.getRpcConfig()
			.isEnableAuth()) {
			rpcRequest.setCredentials(rpcApplication.getRpcConfig()
				                          .getClientCredentials());
		}

		long startTime = System.currentTimeMillis();
		RpcResponse rpcResponse;
		try {
			// 重试策略
			RetryStrategy retryStrategy =
				RetryStrategyFactory.getInstance(rpcApplication.getRpcConfig().getRetryStrategy());
			rpcResponse = retryStrategy.executeWithRetry(rpcRequest.getServiceName(), () -> invoke(rpcRequest));

			// 记录成功指标
			MetricsCollector.recordSuccess(rpcRequest.getServiceName(), rpcRequest.getMethodName(),
			                               System.currentTimeMillis() - startTime);
		} catch (Exception e) {
			log.error("调用远程服务失败: {}", e.getMessage());

			// 记录失败指标
			MetricsCollector.recordFailure(rpcRequest.getServiceName(), rpcRequest.getMethodName(),
			                               System.currentTimeMillis() - startTime, e.getClass()
				                               .getSimpleName());

			// 容错处理
			TolerantStrategy instance =
				TolerantStrategyFactory.getInstance(rpcApplication.getRpcConfig().getTolerantStrategy());
			Map<String, Object> context = new HashMap<>();
			context.put("rpcRequest", rpcRequest);
			context.put("rpcApplication", rpcApplication);
			rpcResponse = instance.doTolerant(context, e);
		} finally {
			// 结束链路追踪
			traceContext.setEndTime(System.currentTimeMillis());
			log.info("RPC调用链路追踪: {}", traceContext);
			TraceContextManager.clearContext();
		}

		// 如果响应中包含追踪上下文，更新本地上下文
		if (rpcResponse.getTraceContext() != null) {
			TraceContextManager.setContext(rpcResponse.getTraceContext());
		}

		return rpcResponse.getData();
	}

	public RpcResponse invoke(RpcRequest rpcRequest) {
		try {
			Serializer serializer = SerializerFactory.getInstance(rpcApplication.getRpcConfig()
				                                                      .getSerializer());
			byte[] bytes = serializer.serialize(rpcRequest);

			ServiceMetaInfo node = currentNode(rpcRequest);

			String serviceAddress = node.getServiceAddress();
			log.info("调用远程服务: {}:{}", serviceAddress, rpcRequest.getMethodName());

			String protocolName = rpcApplication.getRpcConfig()
				.getProtocolName();
			if ("http".equals(protocolName)) {
				return sendHttpRequest(serviceAddress, bytes);
			} else if ("tcp".equals(protocolName)) {
				return sendTcpRequest(node, bytes);
			}
			throw new RpcException(ExceptionCode.PARAMETER_ERROR.getCode(), "不支持的协议类型: " + protocolName);
		} catch (RpcException e) {
			throw e;
		} catch (Exception e) {
			throw new RpcException(ExceptionCode.SERVICE_CALL_FAILED.getCode(), "服务调用失败", e);
		}
	}

	/**
	 * 当前节点
	 *
	 * @param rpcRequest
	 * @return
	 */
	private ServiceMetaInfo currentNode(RpcRequest rpcRequest) {
		try {
			ServiceMetaInfo serviceMetaInfo = new ServiceMetaInfo();
			serviceMetaInfo.setServiceName(rpcRequest.getServiceName());
			serviceMetaInfo.setServiceVersion(rpcApplication.getRegistryConfig()
				                                  .getServiceVersion());
			serviceMetaInfo.setServiceGroup(rpcApplication.getRegistryConfig()
				                                .getGroup());
			// 发现服务
			List<ServiceMetaInfo> serviceMetaInfos = serviceDiscovery(serviceMetaInfo.getServiceKey());
			// 负载均衡
			return loadBalance(rpcRequest.getServiceName(), serviceMetaInfos);
		} catch (RpcException e) {
			throw e;
		} catch (Exception e) {
			throw new RpcException(ExceptionCode.DISCOVERY_ERROR.getCode(), "服务发现失败", e);
		}
	}

	/**
	 * 负载均衡
	 *
	 * @param serviceName
	 * @param serviceMetaInfos
	 * @return ServiceMetaInfo
	 */
	private ServiceMetaInfo loadBalance(String serviceName, List<ServiceMetaInfo> serviceMetaInfos) {
		try {
			LoadBalancer loadBalancer = LoadBalancerFactory.getInstance(rpcApplication.getRpcConfig()
				                                                            .getLoadBalancer());
			HashMap<String, Object> map = new HashMap<>();
			map.put("serviceName", serviceName);
			return loadBalancer.select(map, serviceMetaInfos);
		} catch (Exception e) {
			throw new RpcException(ExceptionCode.SYSTEM_ERROR.getCode(), "负载均衡失败", e);
		}
	}

	/**
	 * 服务发现
	 *
	 * @param serviceKey
	 * @return List<ServiceMetaInfo>
	 */
	private List<ServiceMetaInfo> serviceDiscovery(String serviceKey) {
		try {
			Registry registry = RegistryFactory.getInstance(rpcApplication.getRpcConfig()
				                                                .getRegistry());
			List<ServiceMetaInfo> serviceMetaInfos = registry.serviceDiscovery(serviceKey);
			log.debug("服务发现: {}", serviceMetaInfos);
			if (serviceMetaInfos.isEmpty()) {
				throw new RpcException(ExceptionCode.SERVICE_NOT_FOUND.getCode(), "未发现服务: " + serviceKey);
			}
			return serviceMetaInfos;
		} catch (RpcException e) {
			throw e;
		} catch (Exception e) {
			throw new RpcException(ExceptionCode.DISCOVERY_ERROR.getCode(), "服务发现过程出错", e);
		}
	}

	/**
	 * 发送http请求
	 * 此方法用于向指定的服务地址发送HTTP请求，并接收响应结果
	 * 它主要用于实现RPC调用过程中的请求发送和响应处理
	 *
	 * @param serviceAddress 服务地址，格式为http://host:port/path，指定了请求的目标服务
	 * @param bytes 请求体内容，以字节数组形式传入，包含了RPC调用的相关信息
	 * @return Object 返回处理后的响应对象，具体类型取决于反序列化结果
	 */
	private RpcResponse sendHttpRequest(String serviceAddress, byte[] bytes) {
		try (HttpResponse httpResponse = HttpRequest.post(serviceAddress).body(bytes).execute()) {
			byte[] bodyBytes = httpResponse.bodyBytes();
			Serializer serializer = SerializerFactory.getInstance(rpcApplication.getRpcConfig().getSerializer());
			return serializer.deserialize(bodyBytes, RpcResponse.class);
		} catch (Exception e) {
			throw new RpcException(ExceptionCode.NETWORK_ERROR.getCode(), "HTTP请求发送失败: " + serviceAddress, e);
		}
	}

	/**
	 * 发送tcp请求
	 */
	private RpcResponse sendTcpRequest(ServiceMetaInfo node, byte[] bytes) {
		try {
			return RpcTcpService.getRpcResponse(node, bytes, rpcApplication);
		} catch (Exception e) {
			throw new RpcException(ExceptionCode.NETWORK_ERROR.getCode(),
			                       "TCP请求发送失败: " + node.getServiceAddress(), e);
		}
	}

}